/*
 * JSOOFacts.java
 *
 * Copyright (c) 2009 GradSoft  Ukraine
 * All Rights Reserved
 */


package ua.gradsoft.misc.jsoo;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import ua.gradsoft.javachecker.EntityNotFoundException;
import ua.gradsoft.javachecker.models.AnalyzedUnitRef;
import ua.gradsoft.javachecker.models.JavaExpressionKind;
import ua.gradsoft.javachecker.models.JavaExpressionModel;
import ua.gradsoft.javachecker.models.JavaMethodModel;
import ua.gradsoft.javachecker.models.JavaPlaceContext;
import ua.gradsoft.javachecker.models.JavaTypeModel;
import ua.gradsoft.javachecker.util.Holder;
import ua.gradsoft.termware.DefaultFacts;
import ua.gradsoft.termware.Term;
import ua.gradsoft.termware.TermWareException;
import ua.gradsoft.termware.TransformationContext;
import ua.gradsoft.termware.exceptions.RuntimeAssertException;

/**
 *Facts for JPE
 * @author Ruslan Shevchenko
 */
public class JSOOFacts extends DefaultFacts
{
    
    public JSOOFacts() throws TermWareException
    { super(); }
    
    public void setConfiguration(Configuration configuration)
    {
      configuration_=configuration;  
      unitsToProcess_ = new LinkedList<AnalyzedUnitRef>();
    }    
    
    //
    // Facts methods
    //
    
    
    public List<AnalyzedUnitRef> getUnitsToProcess()
    { return unitsToProcess_; }



    public boolean isOverloadedBinaryExpression(TransformationContext ctx,
                                                Term frs, Term snd,
                                                String operation,
                                                Term exprCtx, Term fn)
                                                  throws TermWareException, JSOOProcessingException
    { return isOverloadedBinaryExpression(ctx,frs,snd,operation,exprCtx,fn,null); }



    public boolean isOverloadedBinaryExpression(TransformationContext ctx,
                                                Term frs, Term snd,
                                                String operation,
                                                Term exprCtx, Term fn,
                                                Holder<JavaMethodModel> methodHolder)
                                                  throws TermWareException, JSOOProcessingException
    {
     //  System.err.println("is overloaded binary exression for ");
     //  frs.println(System.err);
     //  snd.println(System.err);
       if (!fn.isX()) {
           throw new RuntimeAssertException("last argument of isOverloadedBinaryExpression is not free variable");
       }
       if (!exprCtx.isJavaObject()) {
           throw new RuntimeAssertException("fourth argument of isOverloadedBinaryExpression is not expression context");
       }
       Object o = exprCtx.getJavaObject();
       JavaExpressionModel exprm;
       if (o instanceof JavaExpressionModel) {
           exprm = (JavaExpressionModel)o;
       }else if (o instanceof JavaPlaceContext) {
           JavaPlaceContext cth = (JavaPlaceContext)exprCtx.getJavaObject();
           exprm = cth.getExpressionModel();
       }else{
           throw new RuntimeAssertException("fourth argument of isOverloadedBinaryExpression is not expression context");
       }
       List<JavaExpressionModel> submodels;
       try {
          submodels = exprm.getSubExpressions();
       }catch(EntityNotFoundException ex){
           throw new JSOOProcessingException("error during analyzing",ex);
       }
       JavaExpressionModel frse = submodels.get(0);
       JavaExpressionModel snde = submodels.get(1);
       JavaMethodModel model = null;
       try {
         JavaTypeModel frsetype = getExprType(frse);
         if (isPrimitiveOrBoxedPrimitive(frsetype)) {
           return false;
         }
         model = JSOOUtils.findBestAnnotatedApplicableMethod(
                                        operation, frsetype, 
                                        Collections.singletonList(getExprType(snde)));
       }catch(EntityNotFoundException ex){
           throw new JSOOProcessingException("Can't determinate type of expression",ex);
       }
       if (model!=null) {         
          ctx.getCurrentSubstitution().put(fn, JSOOUtils.createIdentifier(model.getName()));          
          if (methodHolder!=null) {
              methodHolder.setValue(model);
          }
          return true;
       }else{
           return false;
       }
    }

    public boolean isOverloadedUnaryExpression(TransformationContext ctx,
                                                Term frs,
                                                String operation,
                                                Term exprCtx, Term fn)
                                                  throws TermWareException, JSOOProcessingException
    {
       if (!fn.isX()) {
            throw new RuntimeAssertException("last argument of isOverloadedBinaryExpression is not free variable");
       }
       if (!exprCtx.isJavaObject()) {
          throw new RuntimeAssertException("fourth argument of isOverloadedBinaryExpression is not expression context");
       }
       JavaPlaceContext cth = (JavaPlaceContext)exprCtx.getJavaObject();
       List<JavaExpressionModel> subs = null;
       try {
         subs = cth.getExpressionModel().getSubExpressions();
       }catch(EntityNotFoundException ex){
         throw new JSOOProcessingException("error during parsing of expressions",ex);
       }
       JavaExpressionModel frse = subs.get(0);
       JavaMethodModel model = null;
       try {
         JavaTypeModel frsetype = getExprType(frse);
         if (isPrimitiveOrBoxedPrimitive(frsetype)) {
           return false;
         }
         model = JSOOUtils.findBestAnnotatedApplicableMethod(
                                        operation, frse.getType(), null);
       }catch(EntityNotFoundException ex){
           throw new JSOOProcessingException("Can't determinate type of expression",ex);
       }
       if (model!=null) {          
          ctx.getCurrentSubstitution().put(fn,JSOOUtils.createIdentifier(model.getName()));          
          return true;
       }else{
           return false;
       }
    }

    public boolean isOverloadedArrayIndexExpression(TransformationContext ctx,
                                                Term a,
                                                Term i,
                                                Term exprCtx,
                                                Term fn)
                                                  throws TermWareException, JSOOProcessingException
    { return isOverloadedArrayIndexExpression(ctx,a,i,exprCtx,fn,null); }


    public boolean isOverloadedArrayIndexExpression(TransformationContext ctx,
                                                Term a,
                                                Term i,
                                                Term exprCtx, 
                                                Term fn,
                                                Holder<JavaMethodModel> methodHolder)
                                                  throws TermWareException, JSOOProcessingException
    {
     //  System.err.println("Call of isOverloadedArrayIndexExpression for ");
     //  a.println(System.err);
     //  i.println(System.err);
       if (!exprCtx.isJavaObject()) {
           throw new RuntimeAssertException("fourth agument of isOverloadedBinaryExpression is not expression context");
       }
       JavaPlaceContext cth = (JavaPlaceContext)exprCtx.getJavaObject();
       List<JavaExpressionModel> submodels;
       JavaTypeModel aetype = null;
       try {
          submodels = cth.getExpressionModel().getSubExpressions();
          aetype = getExprType(submodels.get(0));
       }catch(EntityNotFoundException ex){
           throw new JSOOProcessingException("error during analyzing",ex);
       }
       if (aetype.isArray()) {
           // it's normal array
           return false;
       }
       boolean retval = isOverloadedBinaryExpression(ctx,a,i,"[]",exprCtx,fn, methodHolder);
    //   System.err.println("isOverloadedArrayIndexExpression resullt is "+retval);
       return retval;
    }

    public boolean isOverloadedSetArrayIndexExpression(TransformationContext ctx,
                                                Term a,
                                                Term i,
                                                Term right,
                                                Term exprCtx, Term fn)
                                                  throws TermWareException, JSOOProcessingException
    {
       //System.err.println("Call of isOverloadedSetArrayIndexExpression for ");
       //a.println(System.err);
       //i.println(System.err);

        if (!exprCtx.isJavaObject()) {
           throw new RuntimeAssertException("fourth argument of isOverloadedBinaryExpression is not expression context");
       }
       JavaPlaceContext cth = (JavaPlaceContext)exprCtx.getJavaObject();
       List<JavaExpressionModel> assignSubmodels;
       List<JavaExpressionModel> arrayIndexSubmodels;
       JavaTypeModel aetype = null;
       try {
          assignSubmodels = cth.getExpressionModel().getSubExpressions();
          arrayIndexSubmodels = assignSubmodels.get(0).getSubExpressions();
          aetype = getExprType(arrayIndexSubmodels.get(0));
       }catch(EntityNotFoundException ex){
           throw new JSOOProcessingException("error during analyzing",ex);
       }
       if (aetype.isArray()) {
           // it's normal array
           return false;
       }
       // it;s not array, so let's search
       try {
         JavaTypeModel frsType = aetype;
         JavaTypeModel sndType = getExprType(arrayIndexSubmodels.get(1));
         JavaTypeModel thrType = getExprType(assignSubmodels.get(1));
         if (isPrimitiveOrBoxedPrimitive(frsType)) {
           return false;
         }
         JavaMethodModel model = JSOOUtils.findBestAnnotatedApplicableMethod(
                                        "[]=", frsType, 
                                        Arrays.asList( sndType, thrType ));
         if (model!=null) {
            ctx.getCurrentSubstitution().put(fn,JSOOUtils.createIdentifier(model.getName()));
            return true;
         }

       }catch(EntityNotFoundException ex){
           throw new JSOOProcessingException("not all resolved",ex);
       }
       return false;
    }

    private JavaTypeModel getExprType(JavaExpressionModel expr) 
            throws TermWareException, EntityNotFoundException, JSOOProcessingException
    {
       if (expr.getKind()==JavaExpressionKind.ARRAY_INDEX) {
           // this can be overloaded expression, so we recive exception during finding type.
           // this problem will not be a problem for 'real' interpetation.
           List<JavaExpressionModel> subexprs = expr.getSubExpressions();
           JavaExpressionModel frse = subexprs.get(0);
           JavaExpressionModel snde = subexprs.get(1);
           JavaTypeModel frsetype = getExprType(frse);
           if (frsetype.isArray()) {
               return expr.getType();
           }else{
             // check for overladed "[]"
             JavaMethodModel m = JSOOUtils.findBestAnnotatedApplicableMethod(
                                       "[]", frsetype, 
                                       Collections.singletonList(getExprType(snde)));
             if (m==null) {
                 // not found.
                 //so, next will throw errors;
                 //throw new JSOOProcessingException("Can;t find overloaded '[]'");
                 return expr.getType();
             }else{
                 return m.getResultType();
             }
           }
       }else{
           return expr.getType();
       }
    }

    private boolean isPrimitiveOrBoxedPrimitive(JavaTypeModel t)
    {
      return (t.isPrimitiveType()
                 ||
              t.getFullName().equals("java.lang.Integer")
               ||
              t.getFullName().equals("java.lang.Short")
                ||
              t.getFullName().equals("java.lang.Long")
               ||
              t.getFullName().equals("java.lang.Double")
               ||
              t.getFullName().equals("java.lang.Float")
               ||
              t.getFullName().equals("java.lang.String")
           );
    }

    // not used in JSOO, 
    private Configuration configuration_;
    
    private List<AnalyzedUnitRef>  unitsToProcess_;
    
    
}
